#include "ICMPScanner.h"
#include <iostream>

ICMPScanner::ICMPScanner()
{
    Init();
}

void ICMPScanner::Init()
{
    //创建原始套接字
    sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    //设置接收超时选项
    int iTimeout = 1000;
    setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout));
}

void ICMPScanner::SendRequest(unsigned long DestIP)
{
    int iResult;

    sockaddr_in addrDest, addrSrc;
    DECODE_RESULT stDecodeResult;

    memset(&stDecodeResult, 0, sizeof(DECODE_RESULT));

    //需要扫描的ip，根据自己的需要来赋值吧
    addrDest.sin_addr.S_un.S_addr = DestIP;
    addrDest.sin_family = AF_INET;

    // 发送ICMP Echo请求
    iResult = SendEchoRequest(sockRaw, &addrDest);
    if (iResult == SOCKET_ERROR)
    {
        if (WSAGetLastError() == WSAEHOSTUNREACH) {
            printf("目的主机不可达，traceroute探测结束！");
            OnResult.Broadcast(inet_ntoa(addrDest.sin_addr), false);
            return;
        }
    }
    //接收ICMP的EchoReply数据报
    iResult = RecvEchoReply(sockRaw, &addrSrc, &addrDest, &stDecodeResult);
}

void ICMPScanner::SendRequest(const char* DestIPStr)
{
    SendRequest(inet_addr(DestIPStr));
}

//计算检验和函数
USHORT ICMPScanner::CheckSum(USHORT* buffer, int size)
{
    unsigned long cksum = 0;
    while (size > 1)
    {
        cksum += *buffer++;
        size -= sizeof(USHORT);
    }
    if (size)
    {
        cksum += *(UCHAR*)buffer;
    }
    while (cksum >> 16)
        cksum = (cksum >> 16) + (cksum & 0xffff);
    return (USHORT)(~cksum);
}

int ICMPScanner::SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)
{
    int nRet; //
    char IcmpSendBuf[sizeof(ICMPHEADER)];//存放包的缓存区

    //填充数据包
    memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));
    ICMPHEADER* pIcmpHeader = (ICMPHEADER*)IcmpSendBuf;
    pIcmpHeader->i_type = 8; //回写请求消息
    pIcmpHeader->i_code = 0;
    pIcmpHeader->i_id = (USHORT)GetCurrentProcessId();//可以随便给，这里为了方便就给当前线程号
    pIcmpHeader->i_seq = 0x0;//
    pIcmpHeader->i_cksum = 0;
    pIcmpHeader->timestamp = 0x01020304;//数据，随意，大小也是随意，这里我定义的4B
    pIcmpHeader->i_cksum = CheckSum((USHORT*)pIcmpHeader, sizeof(ICMPHEADER));//校验和计算，函数如下：

    nRet = sendto(s, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN));//发送

    if (nRet == SOCKET_ERROR)
    {
        std::cout << "sento Error" << std::endl;
        return -1;
    }
    return 0;
}

/********************************************************
函数名：DecodeIcmpResponse
输入参数：char* pBuf：接收到的原始数据包（包括IP首部）
int iPacketSize：原始数据包大小
DECODE_RESULT *stDecodeResult：解析结构指针
输出参数：操作成功的标志，true：成功，false：失败
功能：解析收到的原始数据包，将收到的ICMP错误报文和响应报文分别处理
*********************************************************/
BOOL ICMPScanner::DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)
{
    IPHEADER* pIpHdr = (IPHEADER*)pBuf;
    int iIpHdrLen = 20;//ip头部，固定20字节

    //ip首部占用20字节，定位到icmp报文
    ICMPHEADER* pIcmpHdr = (ICMPHEADER*)(pBuf + iIpHdrLen);
    USHORT usID, usSquNo;
    if (pIcmpHdr->i_type == 0)//回写应答
    {
        usID = pIcmpHdr->i_id;
        usSquNo = pIcmpHdr->i_seq;
    }
    else
        return FALSE;
    //处理正确收到的ICMP数据报，因为type=0的icmp只有一种报文，所以不用检查code
    if (pIcmpHdr->i_type == 0)
    {
        //返回解码结果
        stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
        stDecodeResult.dwRoundTripTime = GetTickCount() - stDecodeResult.dwRoundTripTime;
        return TRUE;
    }
    return FALSE;
}

/********************************************************
函数名：RecvEchoReply
输入参数：SOCKET s：原始套接字
SOCKADDR_IN *saFrom：数据包的来源地址
SOCKADDR_IN *saDest：数据包的目标地址
DECODE_RESULT *stDecodeResult：解析结构指针
输出参数：操作成功的标志，true：成功，false：失败
功能：接收数据，并调用DecodeIcmpResponse对接收到的ICMP响应进行解析
*********************************************************/
DWORD ICMPScanner::RecvEchoReply(SOCKET s, SOCKADDR_IN* saFrom, SOCKADDR_IN* saDest, DECODE_RESULT* stDecodeResult)
{
    int res = 0;
    int nRet;
    int nAddrLen = sizeof(struct sockaddr_in);

    //创建ICMP包接收缓冲区
    char IcmpRecvBuf[1024];
    memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));

    // 接收
    nRet = recvfrom(s,					// socket
                    (LPSTR)&IcmpRecvBuf,	// buffer
                    1024,	// size of buffer
                    0,					// flags
                    (LPSOCKADDR)saFrom,	// From address
                    &nAddrLen);			// pointer to address len


    //打印输出
    if (nRet != SOCKET_ERROR) //接收没有错误
    {
        //解码得到的数据包，如果解码正确则跳出接收循环发送下一个EchoRequest包
        if (DecodeIcmpResponse(IcmpRecvBuf, nRet, *stDecodeResult))
        {
            if (stDecodeResult->dwIPaddr.s_addr == saDest->sin_addr.s_addr)
            {
                printf("该主机处于活动状态\n");
                OnResult.Broadcast(inet_ntoa(saDest->sin_addr), true);
                return 1;
            }
        }
        else
        {
            res = -1;
        }
    }
    else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时，一般情况下，不可达可以收到目的地不可达icmp报文，但如果需要扫描ip与本机处于同一局域网，则主//机在发送icmp之前，回先发送arp请求目的ip物理地址，如果该ip存在，则才会继续发送icmp报文，如果不存在，则不会有后续的，这里就是处理这种情况
    {
        printf("该主机不处于活动状态\n");
        res = 0;
    }
    else
    {
        printf("recvfrom函数调用错误，错误号： %d\n", WSAGetLastError());
        res = -1;
    }
    OnResult.Broadcast(inet_ntoa(saDest->sin_addr), false);
    return res;
}
